/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2022-2022.  All rights reserved.2023.
 */

package service

import (
	"prim-server/common"
	"prim-server/dao"
	"prim-server/logger"
	"prim-server/model"
	"prim-server/util"
	"prim-server/util/toolcbc"
	"strings"
	"time"

	bssModel "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/bss/v2/model"
	"github.com/spf13/cast"
)

const (
	//云上订单
	platformTypeOfCloud = "1"
	//自有订单
	platformTypeOfSelf = "2"
	//账单计费模式
	//包周期账单
	chargeModeOfOnePay = "1"
	//按需账单
	chargeModeOfOnDemand = "3"
	//预留实例
	chargeModeOfReserved = "10"

	defaultOffset                 = int32(0)
	defaultGetBillRecordLimit     = int32(1000)
	defautlGetResourceRecordLimit = int32(1000)

	//已支付
	billStatusOfPaid = int32(1)
	//未结清
	billStatusOfUnsettled = int32(2)
	//未结算
	billStatusOfNotPaid = int32(3)

	emptyString      = ""
	timeFormatLayout = "2006-01-02T15:04:05Z"

	//拉取云上账单失败重试次数
	maxRequestTimes = 3
)

type CombinedCloudResourceFeeRecord struct {
	//以资源消费记录为准时使用
	CloudResource []bssModel.ResFeeRecordV2
	OrderId       []string
	UserId        string
	BillCycle     string
	Currency      string
}

type CombinedCloudBillRecord struct {
	//以流水账单为准时使用
	CloudBillRecord []bssModel.MonthlyBillRecord
	CloudResourceId []string
	OrderId         []string
	UserId          string
	BillCycle       string
	Currency        string
}

func GetChargeModeOfOnePay() (chargeMode string) {
	return chargeModeOfOnePay
}
func GetchargeModeOfOnDemand() (chargeMode string) {
	return chargeModeOfOnDemand
}
func GetchargeModeOfReserved() (chargeMode string) {
	return chargeModeOfReserved
}
func IsChargeModeOfOnePay(chargeMode string) bool {
	return chargeMode == chargeModeOfOnePay
}
func IsChargeModeOfOnDemand(chargeMode string) bool {
	return chargeMode == chargeModeOfOnDemand
}
func IsChargeModeOfReserved(chargeMode string) bool {
	return chargeMode == chargeModeOfReserved
}

// 创建基础流水账单对象
func CreateBasicBill(billCycle, userId, platformType, cloudUserId, orderId, tradeId, tradeTime string) *model.Bill {
	bill := &model.Bill{}
	bill.BillBasic = model.BillBasic{
		BillCycle:    billCycle,
		PlatformType: platformType,
		BillId:       util.GetUUID32(),
		UserId:       userId,
		CloudUserId:  cloudUserId,
		OrderId:      orderId,
		TradeId:      tradeId,
		TradeTime:    tradeTime,
	}
	//自有平台tradeId自行生成
	if platformType == platformTypeOfSelf {
		bill.TradeId = util.GetUUID32()
	}
	return bill
}

func CreateAndSaveBillWithCloudBill(cloudBills *CombinedCloudBillRecord) error {
	if cloudBills == nil {
		return nil
	}
	if len(cloudBills.CloudBillRecord) == 0 {
		return nil
	}
	userResourceId := false
	if len(cloudBills.CloudBillRecord) == len(cloudBills.CloudResourceId) {
		userResourceId = true
	}
	billStatus := billStatusOfNotPaid
	platformType := platformTypeOfCloud
	bills := make([]model.Bill, len(cloudBills.CloudBillRecord))
	for index, bill := range cloudBills.CloudBillRecord {
		orderId := (*cloudBills).OrderId[index]
		chargeMode := cast.ToString(*bill.ChargingMode)
		cloudOrderId := ""
		if IsChargeModeOfOnePay(chargeMode) {
			cloudOrderId = *bill.TradeId
		}
		addBill := CreateBasicBill((*cloudBills).BillCycle, (*cloudBills).UserId, platformType,
			*bill.CustomerId, orderId, *bill.TradeId, *bill.TradeTime)
		if userResourceId {
			addBill.AddBillCloudDetail(&cloudOrderId, &cloudBills.CloudResourceId[index])
		} else {
			addBill.AddBillCloudDetail(&cloudOrderId, nil)
		}
		addBill.AddBillServiceDetail(bill.ServiceTypeCode, bill.ServiceTypeName, bill.ResourceTypeCode,
			bill.ResourceTypeName, bill.RegionCode, bill.RegionName)
		addBill.AddBillAmount(bill.ConsumeAmount, bill.OfficialAmount, bill.OfficialDiscountAmount,
			bill.EraseAmount, bill.CashAmount, bill.CreditAmount, bill.CouponAmount, bill.FlexipurchaseCouponAmount,
			bill.StoredValueCardAmount, bill.DebtAmount, bill.WriteoffAmount)

		effectiveTime := GetEffectiveTimeFromConsumeTime(*bill.ConsumeTime)

		addBill.AddBillTradeDetail(&effectiveTime, nil, &cloudBills.Currency, &chargeMode, *bill.BillType, &billStatus)
		bills[index] = *addBill
	}
	err := dao.GetBillDao().SaveBill(bills, (*cloudBills).BillCycle)
	if err != nil {
		logger.Error("create bill with cloud bill error: %v, billCycle: %s, size: %v", err.Error(), (*cloudBills).BillCycle, len(cloudBills.CloudBillRecord))
		return err
	}
	return nil
}

func CreateAndSaveBillWithCloudRecordFeeRecord(cloudResources *CombinedCloudResourceFeeRecord) error {
	if cloudResources == nil {
		return nil
	}
	if len(cloudResources.CloudResource) == 0 {
		return nil
	}
	billStatus := billStatusOfNotPaid
	platformType := platformTypeOfCloud
	bills := make([]model.Bill, len(cloudResources.CloudResource))
	for index, bill := range cloudResources.CloudResource {
		orderId := (*cloudResources).OrderId[index]
		cloudOrderId := ""
		if IsChargeModeOfOnePay(*bill.ChargeMode) {
			cloudOrderId = *bill.TradeId
		}
		addBill := CreateBasicBill((*cloudResources).BillCycle, (*cloudResources).UserId, platformType,
			*bill.CustomerId, orderId, *bill.TradeId, *bill.TradeTime)
		addBill.AddBillCloudDetail(&cloudOrderId, bill.ResourceId)
		addBill.AddBillServiceDetail(bill.CloudServiceType, bill.CloudServiceTypeName, bill.ResourceType,
			bill.ResourceTypeName, bill.Region, bill.RegionName)
		eraseAmount := *bill.OfficialAmount - *bill.Amount
		addBill.AddBillAmount(bill.Amount, bill.OfficialAmount, bill.DiscountAmount,
			&eraseAmount, bill.CashAmount, bill.CreditAmount, bill.CouponAmount, bill.FlexipurchaseCouponAmount,
			bill.StoredCardAmount, bill.DebtAmount, bill.AdjustmentAmount)
		addBill.AddBillTradeDetail(bill.EffectiveTime, bill.ExpireTime, &cloudResources.Currency, bill.ChargeMode, *bill.BillType, &billStatus)
		bills[index] = *addBill
	}
	err := dao.GetBillDao().SaveBill(bills, (*cloudResources).BillCycle)
	if err != nil {
		logger.Error("create bill with cloud resource error: %v, billCycle: %s, size: %v", err.Error(), (*cloudResources).BillCycle, len(cloudResources.CloudResource))
		return err
	}
	return nil
}

// 拉取云上资源消费记录
func PullCloudResourceConsumeRecord(parameter *common.PullResourceRecordParameter) (response *bssModel.ListCustomerselfResourceRecordsResponse, err error) {
	if parameter == nil {
		return nil, nil
	}
	err = nil
	for i := 0; i < maxRequestTimes; i++ {
		response, err = toolcbc.GetResourceRecord(parameter.CloudUserId, parameter.BillCycle,
			parameter.CloudResourceId, parameter.ServiceTypeCode, parameter.RegionCode,
			parameter.TradeId, parameter.BillDateBegin, parameter.BillDateEnd,
			parameter.ChargeMode, parameter.BillType, parameter.Offset, parameter.Limit, parameter.IncludeZeroRecord)
		if err != nil {
			logger.Error("pull cloud resource error: %v, cloudUserId: %s", err.Error(), parameter.CloudUserId)
			response = nil
			time.Sleep(1 * time.Second)
			continue
		} else {
			break
		}
	}
	return response, err
}

// 根据云上tradeId获取资源消费记录
func PullCloudResourceConsumeRecordWithCloudTradeId(cloudUserId, billCycle, cloudTradeId string) (response *bssModel.ListCustomerselfResourceRecordsResponse, err error) {
	if len(cloudUserId) == 0 || len(cloudTradeId) == 0 || len(billCycle) == 0 {
		return nil, nil
	}
	request := &bssModel.ListCustomerselfResourceRecordsRequest{
		Cycle:   billCycle,
		TradeId: &cloudTradeId,
	}
	err = nil
	for i := 0; i < maxRequestTimes; i++ {
		response, err = toolcbc.GetResourceRecordWithParameter(cloudUserId, request)
		if err != nil {
			logger.Error("pull cloud resource error: %v, cloudUserId: %s", err.Error(), cloudUserId)
			response = nil
			time.Sleep(1 * time.Second)
			continue
		} else {
			break
		}
	}
	return response, err
}
func PullCloudResourceConsumeRecordDays(cloudUserId, billCycle string, billDateBegin, billDateEnd *string, offset, limit *int32) (*bssModel.ListCustomerselfResourceRecordsResponse, error) {
	parameter := &common.PullResourceRecordParameter{
		BillAndResourceMixedParameter: common.BillAndResourceMixedParameter{
			CloudUserId:   cloudUserId,
			BillCycle:     billCycle,
			BillDateBegin: billDateEnd,
			BillDateEnd:   billDateEnd,
			Offset:        offset,
			Limit:         limit,
		},
	}
	return PullCloudResourceConsumeRecord(parameter)
}

// 拉取云上流水账单
func PullCloudCustomerBillRecord(parameter *common.PullBillParameter) (response *bssModel.ListCustomerBillsFeeRecordsResponse, err error) {
	if parameter == nil {
		return nil, nil
	}
	err = nil
	for i := 0; i < maxRequestTimes; i++ {
		response, err = toolcbc.GetBiilRecord(parameter.CloudUserId, parameter.BillCycle,
			parameter.ServiceTypeCode, parameter.ResourceTypeCode, parameter.RegionCode, parameter.TradeId, parameter.BillDateBegin, parameter.BillDateEnd,
			parameter.ChargeMode, parameter.BillType, parameter.BillStatus, parameter.Offset, parameter.Limit, parameter.IncludeZeroRecord)
		if err != nil {
			logger.Error("pull cloud bill error: %v", err.Error())
			response = nil
			time.Sleep(1 * time.Second)
			continue
		} else {
			break
		}
	}
	return response, nil
}

func PullCloudCustomerBillRecordDays(cloudUserId, billCycle string, billDateBegin, billDateEnd *string, offset, limit *int32) (*bssModel.ListCustomerBillsFeeRecordsResponse, error) {
	parameter := &common.PullBillParameter{
		BillAndResourceMixedParameter: common.BillAndResourceMixedParameter{
			CloudUserId:   cloudUserId,
			BillCycle:     billCycle,
			BillDateBegin: billDateEnd,
			BillDateEnd:   billDateEnd,
			Offset:        offset,
			Limit:         limit,
		},
	}
	return PullCloudCustomerBillRecord(parameter)
}
func BuildQueryParameterForGetUserBill(billCycle string,
	platformType, billId, UserId, OrderId []string,
	chargeMode, billType, billStatus []int32,
	tradeTimeBegin, tradeTimeEnd *int64,
	page, pageSize *int, timeReverseOrder *bool) *common.QueryBillDetail {
	queryParameter := &common.QueryBillDetail{
		BillCycle:        billCycle,
		PlatformType:     platformType,
		BillId:           billId,
		UserId:           UserId,
		OrderId:          OrderId,
		ChargeMode:       chargeMode,
		BillType:         billType,
		BillStatus:       billStatus,
		TradeTimeBegin:   tradeTimeBegin,
		TradeTimeEnd:     tradeTimeEnd,
		Page:             page,
		PageSize:         pageSize,
		TimeReverseOrder: timeReverseOrder,
	}
	return queryParameter
}
func GetUserBillWithParameter(queryParameter *common.QueryBillDetail) ([]*model.Bill, int, error) {
	if queryParameter == nil {
		return nil, 0, nil
	}
	bill, totalCount, err := dao.GetBillDao().GetBills(queryParameter)
	if err != nil {
		logger.Error("get user bill error: %v", err.Error())
		return nil, 0, err
	}
	return bill, totalCount, nil
}
func GetUserBillAll(billCycle string, platformType, billId, UserId, OrderId []string,
	chargeMode, billType, billStatus []int32,
	tradeTimeBegin, tradeTimeEnd *int64,
	page, pageSize *int, timeReverseOrder *bool) ([]*model.Bill, int, error) {
	return GetUserBillWithParameter(BuildQueryParameterForGetUserBill(billCycle,
		platformType, billId, UserId, OrderId,
		chargeMode, billType, billStatus,
		tradeTimeBegin, tradeTimeEnd,
		page, pageSize, timeReverseOrder))
}
func buildQueryparameterFromQueryBillRequestForCommonUser(req *common.QueryBillCommonRequest) *common.QueryBillDetail {
	if req == nil {
		return nil
	}
	if len(req.BillCycle) == 0 || len(req.UserId) == 0 {
		return nil
	}
	queryParameter := BuildQueryParameterForGetUserBill(req.BillCycle, nil,
		req.BillId, []string{req.UserId}, req.OrderId, req.ChargeMode,
		req.BillType, req.BillStatus, req.TradeTimeBegin,
		req.TradeTimeEnd, req.Page, req.PageSize, req.TimeReverseOrder)
	return queryParameter
}

func buildQueryparameterFromQueryBillRequestForAdmin(req *common.QueryBillRequestForAdmin) *common.QueryBillDetail {
	if req == nil {
		return nil
	}
	if len(req.BillCycle) == 0 {
		return nil
	}
	queryParameter := BuildQueryParameterForGetUserBill(req.BillCycle, req.PlatformType,
		req.BillId, req.QueryUserId, req.OrderId, req.ChargeMode,
		req.BillType, req.BillStatus, req.TradeTimeBegin,
		req.TradeTimeEnd, req.Page, req.PageSize, req.TimeReverseOrder)
	return queryParameter
}
func buildBillCommonInfoFromBill(bill *model.Bill) common.BillCommonInfo {
	addBill := common.BillCommonInfo{
		UserId:                 bill.UserId,
		BillCycle:              bill.BillCycle,
		BillId:                 bill.BillId,
		OrderId:                bill.OrderId,
		ServiceTypeName:        bill.ServiceTypeName,
		ResourceTypeName:       bill.ResourceTypeName,
		RegionName:             bill.RegionName,
		ChargeMode:             bill.ChargeMode,
		EffectiveTime:          bill.EffectiveTime,
		ExpireTime:             bill.ExpireTime,
		TradeTime:              bill.TradeTime,
		BillType:               bill.BillType,
		BillStatus:             bill.BillStatus,
		OfficialAmount:         bill.OfficialAmount,
		OfficialDiscountAmount: bill.OfficialDiscountAmount,
		EraseAmount:            bill.EraseAmount,
		ConsumeAmount:          bill.ConsumeAmount,
		CashAmount:             bill.CashAmount,
		CreditAmount:           bill.CreditAmount,
		CouponAmount:           bill.CouponAmount,
		DebtAmount:             bill.DebtAmount,
	}
	return addBill
}
func BuildResponseForQueryBillCommonUser(req *common.QueryBillCommonRequest) (*common.CommonUserBillQueryResponse, error) {
	if req == nil {
		return nil, nil
	}
	queryParameter := buildQueryparameterFromQueryBillRequestForCommonUser(req)
	if queryParameter == nil {
		return nil, nil
	}
	userBills, totalCount, err := GetUserBillWithParameter(queryParameter)
	if err != nil {
		logger.Error("build response for query bill commonUser error: %v", err.Error())
		return nil, err
	}
	response := &common.CommonUserBillQueryResponse{
		TotalCount: totalCount,
		Size:       len(userBills),
	}
	getCurrency := false
	bills := []common.BillCommonInfo{}
	for _, bill := range userBills {
		if !getCurrency {
			response.Currency = bill.Currency
		}
		addBill := buildBillCommonInfoFromBill(bill)
		bills = append(bills, addBill)
	}
	response.Bill = bills
	return response, nil
}

func BuildResponseForQueryBillAdminUser(req *common.QueryBillRequestForAdmin) (*common.AdminUserBillQueryResponse, error) {
	if req == nil {
		return nil, nil
	}
	queryParameter := buildQueryparameterFromQueryBillRequestForAdmin(req)
	if queryParameter == nil {
		return nil, nil
	}
	userBills, totalCount, err := GetUserBillWithParameter(queryParameter)
	if err != nil {
		logger.Error("build response for query bill adminerror: %v", err.Error())
		return nil, err
	}
	response := &common.AdminUserBillQueryResponse{
		TotalCount: totalCount,
		Size:       len(userBills),
	}
	getCurrency := false
	bills := []common.UserBillAllInfo{}
	for _, bill := range userBills {
		if !getCurrency {
			response.Currency = bill.Currency
		}
		addBill := common.UserBillAllInfo{
			PlatformType:    bill.PlatformType,
			TradeId:         bill.TradeId,
			CloudResourceId: bill.CloudResourceId,
		}
		addBill.Billinfo = buildBillCommonInfoFromBill(bill)
		bills = append(bills, addBill)
	}
	response.Bill = bills
	return response, nil
}

// "2006-01-02 15:04:05" convert to "2006-01-02T15:04:05Z"
func ConvertToUTCStr(str string) string {
	return strings.Replace(str, " ", "T", 1) + "Z"
}

func GetEffectiveTimeFromConsumeTime(consumeTime string) string {
	beginTime := consumeTime[:len(timeFormatLayout)]
	return beginTime
}
